﻿/* 
   Copyright 2011 LUCA Studios LLC & John Evans

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

namespace LUCAStudios.Silverlight.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Markup;
    using DragDrop;

    /// <summary>
    /// A flexible StackPanel-like control that permits docking and orientation functionality
    /// </summary>
    [ContentProperty("Children")]
    public sealed class LUCAStackPanel : ContainerBase
    {
        #region fields
        private Grid _dp;

        private bool _updateAfterLoad;
        private bool _firstLoad;
        private const double DefaultSplitterThickness = 4d;

        private readonly GridLength _defaultGridLength = new GridLength(1, GridUnitType.Star);
        /// <summary>
        /// Backing property for the ShowsPreview dependency property
        /// </summary>
        public static readonly DependencyProperty ShowsPreviewProperty =
            DependencyProperty.Register("ShowsPreview",
                typeof(bool),
                typeof(LUCAStackPanel),
                new PropertyMetadata(false, OnShowsPreviewChanging));

        /// <summary>
        /// Backing property for the Orientation Dependency property
        /// </summary>
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation",
                typeof(LUCAStackPanelOrientation),
                typeof(LUCAStackPanel),
                new PropertyMetadata(LUCAStackPanelOrientation.Horizontal, OnOrientationChanging));


        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public LUCAStackPanel()
        {
            DefaultStyleKey = typeof(LUCAStackPanel);
            AllowDrop = true;
        }

        private static void OnOrientationChanging(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var o = d as LUCAStackPanel;
            if (o == default(LUCAStackPanel)) return;

            if (o._dp == null)
                o._updateAfterLoad = true;
            else
                o.RebuildGridLayout();
        }

        private static void OnShowsPreviewChanging(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var o = d as LUCAStackPanel;
            if (o == default(LUCAStackPanel)) return;
            o.ChangeGridSplitterPreview((bool)e.NewValue);
        }

        private static void InitializeDockedProperties(ContainerBase control)
        {
            if (control == null) throw new ArgumentNullException("control");

            control.RenderTransform = null;
            control.Height = double.NaN;
            control.Width = double.NaN;
            control.HorizontalAlignment = HorizontalAlignment.Stretch;
            control.VerticalAlignment = VerticalAlignment.Stretch;
            control.Margin = new Thickness(0);
            Grid.SetColumn(control, 0);
            Grid.SetRow(control, 0);


            if (control is WindowBase && ((WindowBase)control).VisualState == WindowLayoutState.Floating)
            {
                ((WindowBase)control).VisualState = WindowLayoutState.Docked;
                ((WindowBase)control).IsDragEnabled = false;
            }
        }

        private void ChangeGridSplitterPreview(bool newSetting)
        {
            if (_dp == default(Grid)) return;
            var gs = _dp.FindAllElementsByType<GridSplitter>();

            foreach (var g in gs.ToEnumerable())
            {
                g.ShowsPreview = newSetting;
            }
        }

        /// <summary>
        /// Rebuild the layout of the Grid's children.
        /// </summary>
        private void RebuildGridLayout()
        {
            _dp.Children.Clear();
            _dp.ColumnDefinitions.Clear();
            _dp.RowDefinitions.Clear();

            // call the appropriate rebuild routine
            if (IsHorizontal)
                RebuildGridHorizontal();
            else
                RebuildGridVertical();

#if DEBUG
            LayoutContext.DebugTree(string.Empty, true);
#endif
        }

        private void RebuildGridHorizontal()
        {
            var hasFlexibleChild = false;

            Children
                .ToObservable()
                .Subscribe(i =>
                    {
                        InitializeDockedProperties(i as ContainerBase);

                        _dp.Children.Add(i);

                        if (Children.Count == 1) return; //single child so return              

                        //define the column, with constraints if those are defined
                        _dp.ColumnDefinitions.Add(
                            GetColumnDefinition(i) != default(ColumnDefinition)
                            ? GetColumnDefinition(i)
                            : new ColumnDefinition
                            {
                                Width = _defaultGridLength
                            });

                        if (_dp.ColumnDefinitions.Last().Width.GridUnitType == GridUnitType.Star)
                        {
                            hasFlexibleChild = true;
                        }

                        if (Children.IndexOf(i) == 0) return; //no gridsplitter needed for the first child

                        Grid.SetColumn(i, _dp.ColumnDefinitions.Count - 1);
                        i.Margin = new Thickness(DefaultSplitterThickness, 0, 0, 0);
                        var b = new GridSplitter
                        {
                            Width = DefaultSplitterThickness,
                            Height = double.NaN,
                            VerticalAlignment = VerticalAlignment.Stretch,
                            HorizontalAlignment = HorizontalAlignment.Left,
                            ShowsPreview = ShowsPreview
                        };
                        Grid.SetColumn(b, _dp.ColumnDefinitions.Count - 1);
                        _dp.Children.Add(b);
                    },()=>
                          {
                              //make the last column elastic if no other columns are already elastic
                              if (hasFlexibleChild) return;
                              var rd = _dp.ColumnDefinitions.LastOrDefault();
                              if (rd == null) return;
                              rd.Width = _defaultGridLength;
                          });
        }

        private void RebuildGridVertical()
        {
            var hasFlexibleChild = false;

            Children
                .ToObservable()
                .Subscribe(i =>
                    {
                        InitializeDockedProperties(i as ContainerBase);

                        _dp.Children.Add(i);

                        if (Children.Count == 1) return; //single child so return

                        //define the row, with constraints if those are defined
                        _dp.RowDefinitions.Add(
                            GetRowDefinition(i) != default(RowDefinition)
                            ? GetRowDefinition(i)
                            : new RowDefinition
                            {
                                Height = _defaultGridLength
                            });

                        //check to see if the latest definition is flexible or not
                        if (_dp.RowDefinitions.Last().Height.GridUnitType == GridUnitType.Star)
                        {
                            hasFlexibleChild = true;
                        }

                        if (Children.IndexOf(i) == 0) return; //no gridsplitter needed for the first child

                        Grid.SetRow(i, _dp.RowDefinitions.Count - 1);
                        i.Margin = new Thickness(0, DefaultSplitterThickness, 0, 0);
                        var b = new GridSplitter
                        {
                            Width = double.NaN,
                            Height = DefaultSplitterThickness,
                            HorizontalAlignment = HorizontalAlignment.Stretch,
                            VerticalAlignment = VerticalAlignment.Top,
                            ShowsPreview = ShowsPreview
                        };

                        Grid.SetRow(b, _dp.RowDefinitions.Count - 1);
                        
                        _dp.Children.Add(b);
                    }, () =>
                           {
                               //make the last row elastic if no other rows are already elastic
                               if (hasFlexibleChild) return;
                               var rd = _dp.RowDefinitions.LastOrDefault();
                               if (rd == null) return;
                               rd.Height = _defaultGridLength;
                           });


        }

        // takes the LSP and moves it into a new parent container LSP which is split with the window being dropped
        private void Split(WindowBase windowToDrop, EdgeDockingDropZone dropZone)
        {
            // remove the window from it's current parent container
            windowToDrop.ContainerContext.RemoveChild(windowToDrop);

            UpdateLastKnownDimensions(this, windowToDrop);

            if (windowToDrop is IOrientingControl)
            {
                ((IOrientingControl)windowToDrop).Orientation = Orientation ==
                                                                       LUCAStackPanelOrientation.Horizontal
                                                                           ? System.Windows.Controls.Orientation.Horizontal
                                                                           : System.Windows.Controls.Orientation.Vertical;
            }

            if (Children.Count <= 1)
            {
                switch (dropZone.InsertType)
                {
                    case InsertType.Left:
                    case InsertType.Top:
                        //the dropped window is first in the new stack
                        InsertChild(0, windowToDrop);
                        break;
                    case InsertType.Right:
                    case InsertType.Bottom:
                        //the target window is first in the new stack
                        AddChild(windowToDrop);
                        break;
                    default:
                        throw new LUCAControlException("Incorrect Insert Type");
                }

                windowToDrop.ContainerContext = this;
            }
            else
            {
                // capture the current index of this LSP
                var ix = ContainerContext.Children.IndexOf(this);

                // store the parent container for this LUCAStackPanel
                var plc = ContainerContext;

                // remove this LUCAStackPanel from it's parent container so we can remerge it later into a new LUCAStackPanel
                ContainerContext.RemoveChild(this);

                // create a new LUCAStackPanel for the window and this LSP.  Toggling the orientation from this LSP.
                var newLsp = new LUCAStackPanel
                {
                    Orientation = Orientation == LUCAStackPanelOrientation.Horizontal
                                    ? LUCAStackPanelOrientation.Vertical
                                    : LUCAStackPanelOrientation.Horizontal,
                };

                UpdateLastKnownDimensions(newLsp, windowToDrop);

                //add the new LSP to the original parent container
                plc.InsertChild(ix, newLsp);

                //get the order correct
                if (dropZone.InsertType == InsertType.Top || dropZone.InsertType == InsertType.Left)
                {
                    newLsp.AddChild(windowToDrop);
                    newLsp.AddChild(this);
                }
                else
                {
                    newLsp.AddChild(this);
                    newLsp.AddChild(windowToDrop);
                }

                //assign parent relationships
                ContainerContext = newLsp;
                windowToDrop.ContainerContext = newLsp;

                //if this LSP has only 1 child, then Rebuild the layout so that it is actually removed and replaced by its' child.
                if (Children.Count == 1) RebuildGridLayout();
            }


        }

        // inserts window at a specific location within the LSP stack
        private void Insert(WindowBase windowToDrop, LUCADropZoneBase dropZone)
        {
            int i;
            if (!Int32.TryParse(dropZone.DropZoneAction, out i)) return;

            InsertChild(i, windowToDrop);
        }

        // takes a LSP content item and splits it into a new LSP, combined with the new window
        private void SplitCell(WindowBase windowToDrop, EdgeDockingDropZone dropZone)
        {
            if (!(dropZone.Data is ControlBase)) return;

            //orphan the window being dropped
            windowToDrop.ContainerContext.RemoveChild(windowToDrop);

            if (windowToDrop is IOrientingControl)
            {
                ((IOrientingControl)windowToDrop).Orientation = Orientation ==
                                                                       LUCAStackPanelOrientation.Horizontal
                                                                           ? System.Windows.Controls.Orientation.Horizontal
                                                                           : System.Windows.Controls.Orientation.Vertical;
            }


            var targetWindow = dropZone.Data as ControlBase;

            if (Children.Count <= 1)
            {
                //flip the orientation
                Orientation = Orientation == LUCAStackPanelOrientation.Horizontal
                                  ? LUCAStackPanelOrientation.Vertical
                                  : LUCAStackPanelOrientation.Horizontal;

                UpdateLastKnownDimensions(this, windowToDrop);

                switch (dropZone.InsertType)
                {
                    case InsertType.Left:
                    case InsertType.Top:
                        //the dropped window is first in the new stack
                        InsertChild(0, windowToDrop);
                        break;
                    case InsertType.Right:
                    case InsertType.Bottom:
                        //the target window is first in the new stack
                        AddChild(windowToDrop);
                        break;
                    default:
                        throw new LUCAControlException("Incorrect Insert Type");
                }

                windowToDrop.ContainerContext = this;
            }
            else
            {
                //capture the position of the target window
                var index = Children.IndexOf(targetWindow);

                var newLsp = new LUCAStackPanel
                {
                    Orientation = Orientation == LUCAStackPanelOrientation.Horizontal
                                      ? LUCAStackPanelOrientation.Vertical
                                      : LUCAStackPanelOrientation.Horizontal
                };

                UpdateLastKnownDimensions(newLsp, windowToDrop);

                //insert the new LSP into the parent LSP
                InsertChild(index, newLsp);

                //orphan the targetWindow
                RemoveChild(targetWindow);

                switch (dropZone.InsertType)
                {
                    case InsertType.Left:
                    case InsertType.Top:
                        //the dropped window is first in the new stack
                        newLsp.AddChild(windowToDrop);
                        newLsp.AddChild(targetWindow);
                        break;
                    case InsertType.Right:
                    case InsertType.Bottom:
                        //the target window is first in the new stack
                        newLsp.AddChild(targetWindow);
                        newLsp.AddChild(windowToDrop);
                        break;
                    default:
                        throw new LUCAControlException("Incorrect Insert Type");
                }

                //assign parent relationships
                targetWindow.ContainerContext = newLsp;
                windowToDrop.ContainerContext = newLsp;

            }
        }

        private static void UpdateLastKnownDimensions(ContainerBase container, ControlBase control)
        {
            container.LastKnownHeight = !double.IsNaN(control.Height) ? control.Height : control.ActualHeight;
            container.LastKnownWidth = !double.IsNaN(control.Width) ? control.Width : control.ActualWidth;
        }

        internal bool IsHorizontal
        {
            get { return Orientation == LUCAStackPanelOrientation.Horizontal; }
        }

        //returns true if this LSP is a child of a LUCALayoutPanel.
        private bool IsRootPanel
        {
            get { return ContainerContext is LUCALayoutPanel; }
        }

        #region overrides

        /// <summary>
        /// Called when the LUCA control is loaded
        /// </summary>
        protected override void OnLoaded()
        {
            base.OnLoaded();

            _dp = this.FindElement<Grid>();

            if (_dp == default(Grid))
                throw new LUCAControlException("Unable to find Grid in template");

            LayoutContext.WindowManipulator.RegisterDragDropTarget(this);

            if (!_updateAfterLoad) return;
            _firstLoad = true;
            _updateAfterLoad = false;
            RebuildGridLayout();
        }

        /// <summary>
        /// Called when the LUCA control is unloaded
        /// </summary>
        protected override void OnUnLoaded()
        {
            base.OnUnLoaded();

            LayoutContext.WindowManipulator.UnRegisterDragDropTarget(this);
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass) call <see cref="M:System.Windows.Controls.Control.ApplyTemplate"/>. In simplest terms, this means the method is called just before a UI element displays in an application. For more information, see Remarks.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            //associate new template grid with this control
            //this is needed to support the theming framework which requires rebuilding of templated controls
            if (_firstLoad)
            {
                //unattach children from old grid
                _dp.Children.Clear();
                _dp.ColumnDefinitions.Clear();
                _dp.RowDefinitions.Clear();

                //associate new grid in template
                _dp = this.FindElement<Grid>();

                //rebuild the layout
                RebuildGridLayout();

            }
        }

        /// <summary>
        /// Performs a framework-safe add of a child to the collection
        /// Preferable over direct Add to the Children collection itself,
        /// because this method can be overriden by derived controls
        /// </summary>
        /// <param name="childToAdd"></param>
        public override void AddChild(ControlBase childToAdd)
        {
            // remove the window from it's current parent container
            if (childToAdd.ContainerContext != null)
                childToAdd.ContainerContext.RemoveChild(childToAdd);

            UpdateLastKnownDimensions(this, childToAdd);

            if (childToAdd is IOrientingControl)
            {
                ((IOrientingControl)childToAdd).Orientation = Orientation ==
                                                                       LUCAStackPanelOrientation.Horizontal
                                                                           ? System.Windows.Controls.Orientation.Vertical
                                                                           : System.Windows.Controls.Orientation.Horizontal;
            }

            base.AddChild(childToAdd);
        }

        /// <summary>
        /// Performs a framework-safe insert into the Children collection
        /// Preferable over direct Insert to the Children collection itself,
        /// because this method can be overriden by derived controls
        /// </summary>
        /// <param name="insertAtIndexOf"></param>
        /// <param name="childToInsert"></param>
        public override void InsertChild(int insertAtIndexOf, ControlBase childToInsert)
        {

            // remove the window from it's current parent container
            if (childToInsert.ContainerContext != null)
                childToInsert.ContainerContext.RemoveChild(childToInsert);

            UpdateLastKnownDimensions(this, childToInsert);

            if (childToInsert is IOrientingControl)
            {
                ((IOrientingControl)childToInsert).Orientation = Orientation ==
                                                                       LUCAStackPanelOrientation.Horizontal
                                                                           ? System.Windows.Controls.Orientation.Vertical
                                                                           : System.Windows.Controls.Orientation.Horizontal;
            }

            // insert it into the child heirarchy.  LSP will handle refresh automatically in OnCollectionChanged()
            base.InsertChild(insertAtIndexOf, childToInsert);
        }

        /// <summary>
        /// Performs a safe remove of a given child from the collection.  
        /// Preferable over direct remove from the Children collection itself,
        /// because this method can be overriden by derived controls
        /// </summary>
        /// <param name="childToRemove"></param>
        public override void RemoveChild(ControlBase childToRemove)
        {
            if (childToRemove == null) throw new ArgumentNullException("childToRemove");

            if (!Children.Contains(childToRemove))
                throw new LUCAControlException("child is not part of the Children collection");

            //conditions where a stackpanel is about to go to a single child
            if (Children.Count == 2)
            {
                base.RemoveChild(childToRemove);

                var lastChild = Children[0];

                //collapse nested stack panels
                if (ContainerContext is LUCAStackPanel || (IsRootPanel && lastChild is LUCAStackPanel))
                {
                    Children.Remove(lastChild);
                    lastChild.ContainerContext = null;

                    var index = ContainerContext.Children.IndexOf(this);

                    //if (((LUCAStackPanel)ParentLiquidContainer).IsRootPanel)
                    //    ((LUCAStackPanel)ParentLiquidContainer).Orientation = Orientation;

                    ContainerContext.Children.Remove(this);

                    PreviousContainerContext.ContainerContext.Children.Insert(index, lastChild);
                    lastChild.ContainerContext = ContainerContext;
                }

                //reset the orientation if this stackpanel is the rootpanel
                //if (IsRootPanel)
                //    Orientation = LUCAStackPanelOrientation.Vertical;

                return;
            }

            //conditions where a stackpanel is about to become empty
            if (Children.Count == 1)
            {
                //remove the child
                base.RemoveChild(childToRemove);

                //if not a rootpanel, then remove it as well
                if (!IsRootPanel)
                {
                    ContainerContext.RemoveChild(this);
                }

                return;
            }

            base.RemoveChild(childToRemove);
        }

        /// <summary>
        /// Requires the control to return drop zones when a drag operation begins
        /// </summary>
        /// <param name="sourceData">The IDragDropSource.DragDropData information from the object that was dropped</param>
        /// <returns></returns>
        public override IEnumerable<DropZoneBase> GetDropZones(object sourceData)
        {
            //only WindowBase controls are accepted as drops for an LSP
            if (!(sourceData is WindowBase)) return base.GetDropZones(sourceData);

            var l = new List<DropZoneBase>();

            if (IsRootPanel && LayoutContext.AllowDocking)
            {
                //only a LSP that is a child of the ParentLayout should proceed with returning these drop zones)
                var gt = TransformToVisual(LayoutContext.RootVisual);
                var p = gt.Transform(new Point(0, 0));
                var wd = new Rect(p.X, p.Y, ActualWidth, ActualHeight);

                if (IsHorizontal)
                {
                    l.Add(new EdgeDockingDropZone(InsertType.Top, this, wd, new Thickness(0, -5, 0, 0)) { DropZoneAction = "split" });
                    l.Add(new EdgeDockingDropZone(InsertType.Bottom, this, wd, new Thickness(0, 0, 0, -5)) { DropZoneAction = "split" });
                }
                else
                {
                    l.Add(new EdgeDockingDropZone(InsertType.Left, this, wd, new Thickness(-5, 0, 0, 0)) { DropZoneAction = "split" });
                    l.Add(new EdgeDockingDropZone(InsertType.Right, this, wd, new Thickness(0, 0, -5, 0)) { DropZoneAction = "split" });
                }
            }

            if (AllowDrop)
            {
                if (Children.Count > 1)
                {
                    //add drop zones for inserting elements within the LSP
                    //var gs = _dp.FindAllElementsByType<GridSplitter>().ToEnumerable();  //bad.. brings all nest LSP GridSplitters too...
                    var gs = _dp.Children.FirstOrDefault(o => o is GridSplitter) as GridSplitter;
                    if (gs == null) return base.GetDropZones(sourceData);

                    var gsThickness = (IsHorizontal ? gs.Width : gs.Height) / 2; //get thickness of the gridsplitters
                    var adjThickness = Math.Ceiling((IsHorizontal ? ActualWidth : ActualHeight) / (Children.Count - 1) * .2d);
                    var halfAdjThickness = adjThickness / 2;
                    //#if DEBUG
                    //                Logger.AddEntry("GetDropZones()", String.Format("Added {0} Insert Zones to LSP",gs.Count()));
                    //#endif
                    for (var x = 1; x < Children.Count; x++)
                    {
                        var gt1 = Children[x].TransformToVisual(LayoutContext.RootVisual);
                        var p1 = gt1.Transform(new Point(0, 0));
                        var hd1 = new Rect(p1.X - gsThickness - halfAdjThickness, p1.Y, adjThickness, ActualHeight);
                        var vd1 = new Rect(p1.X, p1.Y - gsThickness - halfAdjThickness, ActualWidth, adjThickness);
                        l.Add(IsHorizontal
                                  ? new InsertingDropZone(this, hd1, true, DropZoneBase.NoOffsets) { DropZoneAction = x.ToString() }
                                  : new InsertingDropZone(this, vd1, false, DropZoneBase.NoOffsets) { DropZoneAction = x.ToString() });
                    }
                }

                //add drop zones for inserting elements at the beginning or end of the LSP
                var first = Children.FirstOrDefault();
                var last = Children.LastOrDefault();
                if (first != default(ControlBase) && last != default(ControlBase))
                {
                    var gt2 = first.TransformToVisual(LayoutContext.RootVisual);
                    var p2 = gt2.Transform(new Point(0, 0));
                    var r2 = new Rect(p2.X, p2.Y, first.ActualWidth, first.ActualHeight);

                    l.Add(new EdgeDockingDropZone(
                        IsHorizontal ? InsertType.Left : InsertType.Top,
                        this,
                        r2,
                        IsHorizontal ? new Thickness(3, 0, 0, 0) : new Thickness(0, 3, 0, 0)
                        ) { DropZoneAction = "firstlast,0" });

                    var gt3 = last.TransformToVisual(LayoutContext.RootVisual);
                    var p3 = gt3.Transform(new Point(0, 0));
                    var r3 = new Rect(p3.X, p3.Y, last.ActualWidth, last.ActualHeight);

                    l.Add(new EdgeDockingDropZone(
                        IsHorizontal ? InsertType.Right : InsertType.Bottom,
                        this,
                        r3,
                        IsHorizontal ? new Thickness(0, 0, 3, 0) : new Thickness(0, 0, 0, 3)
                        ) { DropZoneAction = "firstlast,99" });
                }

                //add drop zones for splitting and individual column/row of the LSP into a new LSP containing the dropped content along with
                //the original content of that column/row

                foreach (var child in Children)
                {
                    var gt4 = child.TransformToVisual(LayoutContext.RootVisual);
                    var p4 = gt4.Transform(new Point(0, 0));
                    var r4 = new Rect(p4.X, p4.Y, child.ActualWidth, child.ActualHeight);
                    l.Add(new EdgeDockingDropZone(
                              IsHorizontal ? InsertType.Top : InsertType.Left,
                              this,
                              r4,
                              IsHorizontal ? new Thickness(0, 5, 0, 0) : new Thickness(5, 0, 0, 0)
                              ) { DropZoneAction = "splitcell", Data = child });
                    l.Add(new EdgeDockingDropZone(
                              IsHorizontal ? InsertType.Bottom : InsertType.Right,
                              this,
                              r4,
                              IsHorizontal ? new Thickness(0, 0, 0, 5) : new Thickness(0, 0, 5, 0)
                              ) { DropZoneAction = "splitcell", Data = child });
                }
            }

            l.AddRange(base.GetDropZones(sourceData));
            return l;
        }

        /// <summary>
        /// Called when the Children collection changes
        /// </summary>
        /// <param name="e"></param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (_dp != default(Grid))
                RebuildGridLayout();
            else
                _updateAfterLoad = true;

            base.OnCollectionChanged(e);
        }

        /// <summary>
        /// Called when a IDragDropSource is dropped on the IDragDropTarget (in any defined drop zone {see 'GetDropZones() method'})
        /// </summary>
        /// <param name="dropZone">The dropzone object that the IDragDropSource dropped into</param>
        /// <param name="dropData">The data associated with the IDragDropSource.DragDropData.  Usually the IDragDropSource object itself.</param>
        public override void OnDrop(DropZoneBase dropZone, object dropData)
        {
            var wb = dropData as WindowBase;

            if (wb == null) return; //invalid drop data; ignore

            //split
            if (dropZone is EdgeDockingDropZone && ((EdgeDockingDropZone)dropZone).DropZoneAction == "split")
            {
                var dz = dropZone as EdgeDockingDropZone;
                Split(wb, dz);
            }
            else if (dropZone is InsertingDropZone)
            {
                var dz = dropZone as InsertingDropZone;
                Insert(wb, dz);
            }
            else if (dropZone is EdgeDockingDropZone && ((EdgeDockingDropZone)dropZone).DropZoneAction.StartsWith("splitcell"))
            {
                var dz = dropZone as EdgeDockingDropZone;
                SplitCell(wb, dz);
            }
            else if (dropZone is EdgeDockingDropZone && ((EdgeDockingDropZone)dropZone).DropZoneAction.StartsWith("firstlast"))
            {
                var dz = dropZone as EdgeDockingDropZone;
                var ps = dz.DropZoneAction.Split(',');
                if (ps.Length == 2)
                {
                    dz.DropZoneAction = ps[1]; //replace the id with the second parameter, which is the index where we want to insert or add
                    if (dz.DropZoneAction == "0")
                        Insert(wb, dz);
                    else
                        AddChild(wb);
                }
            }

            base.OnDrop(dropZone, dropData);
        }

        #endregion

        #region properties

        /// <summary>
        /// Dependency property which Gets or Sets whether any resizing operations within the 
        /// LUCAStackPanel should be previewed or not.  Default is false, meaning the content
        /// will resize in real-time while the resize operation takes place.
        /// </summary>
        public bool ShowsPreview
        {
            get { return (bool)GetValue(ShowsPreviewProperty); }
            set { SetValue(ShowsPreviewProperty, value); }
        }

        /// <summary>
        /// Dependency property which Gets or Sets the layout orientation of the LUCAStackPanel.
        /// Horizontal (left to right) or Vertical (top to bottom)
        /// </summary>
        public LUCAStackPanelOrientation Orientation
        {
            get { return (LUCAStackPanelOrientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        #endregion

        #region Attached Dependency Properties
        /// <summary>
        /// Backing property for the ColumnDefinition Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty ColumnDefinitionProperty =
            DependencyProperty.RegisterAttached("ColumnDefinition", typeof(ColumnDefinition), typeof(LUCAStackPanel), new PropertyMetadata(default(ColumnDefinition)));

        /// <summary>
        /// Sets a Column Definition on the given ControlBase element.  This is later used by the StackPanel to determine
        /// how to define the properties of the column that the control will reside in if it is docked
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        public static void SetColumnDefinition(ControlBase element, ColumnDefinition value)
        {
            element.SetValue(ColumnDefinitionProperty, value);
        }

        /// <summary>
        /// Retrieves any set ColumnDefinition attached to the given ControlBase element
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static ColumnDefinition GetColumnDefinition(ControlBase element)
        {
            return element != null ? (ColumnDefinition)element.GetValue(ColumnDefinitionProperty) : null;
        }

        /// <summary>
        /// Backing property for the RowDefinition Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty RowDefinitionProperty =
            DependencyProperty.RegisterAttached("RowDefinition", typeof(RowDefinition), typeof(LUCAStackPanel), new PropertyMetadata(default(RowDefinition)));

        /// <summary>
        /// Sets a Row Definition on the given ControlBase element.  This is later used by the StackPanel to determine
        /// how to define the properties of the row that the control will reside in if it is docked
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        public static void SetRowDefinition(ControlBase element, RowDefinition value)
        {
            element.SetValue(RowDefinitionProperty, value);
        }

        /// <summary>
        /// Retrieves any set RowDefinition attached to the given ControlBase element
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static RowDefinition GetRowDefinition(ControlBase element)
        {
            return element != null ? (RowDefinition)element.GetValue(RowDefinitionProperty) : null;
        }
        #endregion

    }
}